//Specify in program which shander

#include <math.h>
#include <time.h>
#include "usefulMethods.h"
#include "highprecisiontime.h"
#include "ball.h"
#include "firework.h"
#include "stands.h"
#include "planarmesh.h"
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <stack>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtx/matrix_operation.hpp>
#include "ShaderSupport.h"

using namespace Mouse;

// Methods
int main(int, char**);
void initRendering();
void drawScene();
void handleKeypress(unsigned char, int, int);
void handleSpecialKeypress(int, int, int);
void mouseButton(int, int, int, int);
void mouseMotion(int, int);
void handleResize(int, int);
void setupBalls(int);
void lightWall( float * );
void DrawWalls( bool GodView = false );
void drawBalls( int, bool GodView = false );
void updateBall(int);
bool handleCollisions(Ball&, Vector3, float&, double&);
void drawPlus();
void LoadSkyBox();
void DrawSkyBox(int sceneType);
void DrawColumns();
void DrawFont();
void InitFrameBufferDepthBuffer();
void InitFrameBufferTexture();
void InitFrameBuffer();
void ScreenToFBO(int texId = 0);
void FBOToScreen(bool jumbotron = false, bool copy = false);
void DrawTimeLeft();
void DrawFloor();
void updateTimer(int);
void CopyScene();
void UpdateInstantReplayDisplayArray();
void InitDisplayLists();
void DrawHole();
void DrawMesh();
void DisplayWinScreen();
void DrawPlayer();
void CleanMemory();
void CloseFunc();
void CloseFunc(int);
void launchFireworks(int);
void createCrowd(int);
void DrawTexturedTargets();

// For freetype
const double TIMER_SCALE = 0.005;

// How much mouse affects the game
#define MOUSE_CONSTANT 15

HighPrecisionTime timer = HighPrecisionTime();

Ball yourBall;
Ball warpZone;
vector<Ball> ballsOnField(0);
Vector3 hole = Vector3();    // Hole in the floor, allows you to play under floor

// Meshes for the wall, floors, and jumbotron
PlanarMesh mesh, theFloor, jumbotronMesh;

float lookingAt[2];      // Viewing Vector
float degreesLookingAt;  // Global viewing angle

// For drawing targets
enum { REGULAR, REFLECTION };

// For Skybox
enum {TOP, LEFT, RIGHT, FRONT, BACK, BOTTOM};
double skyboxRotationAngle = 0;

// Jumbotron Dimensions
float jumbotron_width;
float jumbotron_height;

// Holds each skybox image
GLuint _skybox[6];
GLuint _city[6];
GLuint _morning[6];

// Ad on jumbotron
GLuint _ad[1];
// Earth targets
GLuint earthMap[1];

// Earth target rotation angle
double earthsRotation = 0;

// Holds Instant Replay frames
#define NUM_REPLAY_FRAMES 150
unsigned int * Instant_Replay_Insert = new unsigned int[NUM_REPLAY_FRAMES];
unsigned int * Instant_Replay_Display = new unsigned int[NUM_REPLAY_FRAMES];
int insertIndex = 0;  // Current index in the array
int displayIndex = 0;
int startNumber = 0;  // What frame you started viewing at
bool initializing = true;

// Wall Color - Light Brown
static float wallColor[4] = { 0.8f, 0.52f, 0.25f, 1.0f };
// Your Ball: God View - Orange
static float yourBallColor[4] = { 1.0f, 0.5f, 0.0f, 1.0f };
// Warp Zone Color - Purple
static float warpZoneColor[4] = { 1.0f, 0.0f, 1.0f, 1.0f };
// Floor Color - Dark Grey
static float floorColor[4] = { 0.2f, 0.2f, 0.2f, 0.5f };

// Initial light position
static GLfloat global_position[] = { 0, 100, 0, 1.0f };

bool gameWon = false;

// Holds total number of targets
int totalBallsOnField;

// Holds position and velocity for balls for billiards mode
vector<Vector3> tmpPosStorage(0);
vector<Vector3> tmpVelStorage(0);

// Contains all instances of fireworks and stands
vector<firework> activeFireworks(0);
vector<Stands> activeStands(0);

// Shader Variables
bool use_shader = false;
GLuint vertex_shader_id;
GLuint fragment_shader_id;
GLuint shader_program;

stack<glm::mat4> shader_modelview;
stack<glm::mat4> shader_projection;

// Used in noise (fire) shader
int paused_time = 0;
int pause_start;

class Jumbotron
{
	public:
		Jumbotron()
		{
		}

		// Creates a FBO (jumbotron)
		Jumbotron(int numFBOs, int numRBOs, int numTexs)
		{
			glEnable(GL_TEXTURE_2D);

			InitFrameBufferDepthBuffer(numRBOs);
			InitFrameBufferTexture(numTexs);

			// Create FBO
			glGenFramebuffers(numFBOs, &fbo);

			// Bind FBO in order to use it
			glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);

			// Attach the texture fbo_texture to the color buffer in our FBO
			glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fbo_texture, 0);

			// Attach the depth buffer fbo_depth to our frame buffer
			glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fbo_depth);

			glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); // unbind the FBO

			GLenum status = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER); // Check that status of our generated FBO
  
			if (status != GL_FRAMEBUFFER_COMPLETE) // If the FBO does not report back as complete  
			{    
				exit(-1);
			} 

			glDisable(GL_TEXTURE_2D);
		}

		// Bind to the FBO
		void Bind(int texId, int fboNum)
		{
			glEnable(GL_TEXTURE_2D);
			glBindTexture(GL_TEXTURE_2D, texId);

			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);  
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);  
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);  
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

			glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboNum); // Bind our frame buffer for rendering
		}

		// Draw the scene with levels of reflection
		void Draw()
		{
			glPushMatrix();
				glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear the depth and colour buffers 
				glLoadIdentity();

				// Is your ball above the floor or below it
				if (yourBall.aboveFloor)
				{
					gluLookAt(yourBall.getPosition().x, 0, yourBall.getPosition().z, 
						  yourBall.getPosition().x + lookingAt[0], 0, yourBall.getPosition().z + lookingAt[1], 
						  0, 1, 0);
				}
				else
				{
					gluLookAt(yourBall.getPosition().x, -2*BALL_RADIUS, yourBall.getPosition().z, 
						  yourBall.getPosition().x + lookingAt[0], -2*BALL_RADIUS, yourBall.getPosition().z + lookingAt[1], 
						  0, 1, 0);
				}

				// Implement fog
				if (Fog_Is_Active)
				{
					glEnable(GL_FOG);
					glFogi(GL_FOG_MODE, GL_LINEAR);

					glFogf(GL_FOG_START, HALF_MILE/2);
					glFogf(GL_FOG_END, HALF_MILE);
					glFogf(GL_FOG_DENSITY, 0.35f);
					glHint(GL_FOG_HINT, GL_NICEST);
				}

				DrawSkyBox(REGULAR);
				DrawSkyBox(REFLECTION);
	
				DrawWalls();
				glCallList(DisplayListIndex + DRAW_COLUMNS);

				if (Hole_Is_Active)
					glCallList(DisplayListIndex + DRAW_HOLE);

				drawBalls(REGULAR);

				// Draw reflective scene
				glPushMatrix();
					glScaled(1, -1, 1);
					glTranslated(0, 2*BALL_RADIUS, 0);
					DrawWalls();
					glCallList(DisplayListIndex + DRAW_COLUMNS);
					if (Hole_Is_Active) 
						glCallList(DisplayListIndex + DRAW_HOLE);
					drawBalls(REFLECTION);

					//Draw Stands
					for(int i = 0; i < activeStands.size(); i++){
						glPushMatrix();
							glTranslateVector(activeStands[i].getPostition());
							activeStands[i].Draw();
						glPopMatrix();

					}
					
					FBOToScreen(true);
				glPopMatrix();

				//Draw Stands
				for(int i = 0; i < activeStands.size(); i++){
					glPushMatrix();
						glTranslateVector(activeStands[i].getPostition());
						activeStands[i].Draw();
					glPopMatrix();

				}

				glCallList(DisplayListIndex + DRAW_FLOOR);

				FBOToScreen(true); // Place FBO

				// Multiple levels of jumbotrons
				glPushMatrix();
					glScaled(1, -1, 1);
					glTranslated(0, 2*BALL_RADIUS, 0);
					FBOToScreen(true);
				glPopMatrix();

				FBOToScreen(true);

				glPushMatrix();
					glScaled(1, -1, 1);
					glTranslated(0, 2*BALL_RADIUS, 0);
					FBOToScreen(true);
				glPopMatrix();

				for (int i = 0; i < ballsOnField.size(); i++)
				{
					glPushMatrix();
					glTranslateVector(ballsOnField[i].getPosition());
					if (ballsOnField[i].ballHit) ballsOnField[i].renderTimer();
					glPopMatrix();
				}

				glDisable(GL_FOG);
			glPopMatrix();
		}

		// Bind to the screen
		void Unbind()
		{
			glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
			glDisable(GL_TEXTURE_2D);
		}

		GLuint fbo;         // The frame buffer object  
		GLuint fbo_depth;   // The depth buffer for the frame buffer object  
		GLuint fbo_texture; // The texture object to write our frame buffer object to 

	protected:
		void InitFrameBufferDepthBuffer(int numRBOs)
		{
			// Create Render Buffer Object
			glGenRenderbuffers(numRBOs, &fbo_depth);

			// Bind RBO in order to use it
			glBindRenderbuffer(GL_RENDERBUFFER, fbo_depth);

			// Create depth buffer
			// Set the render buffer storage to be a depth component, with a width and height of the window  
			glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, window_width, window_height);

			glBindRenderbuffer(GL_RENDERBUFFER, 0); // Unbind the render buffer 
		}

		void InitFrameBufferTexture(int numTexs)
		{
			// Initialize the texture
			glGenTextures(numTexs, &fbo_texture);
			glBindTexture(GL_TEXTURE_2D, fbo_texture);
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, window_width, window_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);

			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);  
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);  
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);  
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);  
  
			// Unbind the texture  
			glBindTexture(GL_TEXTURE_2D, 0);
		}
} jumbotronObj;

class BasicShader : public ShaderHandles
{
public:
	// Noise Shader
	GLuint modelview_matrix_id;
	GLuint projection_matrix_id;
	GLuint offset_id;
	GLuint mode_id;

	// EarthMap Shader
	GLuint texture_id; // Holds EarthMap.jpg

	// Waving Shader
	GLuint texture_id2; // Holds jumbotron texture
	GLuint wt;          // Wave time
	GLuint ww;          // Wave width
	GLuint wh;          // Wave height

	// Toon Shader
	GLuint lightDir;   // Direction of light

	int mode;

	BasicShader() : ShaderHandles()
	{
		mode_id = projection_matrix_id = modelview_matrix_id = -1;
		mode = 0;
	}

	virtual void InitializeUniforms()
	{
		assert(this->program_id != -1);
		UseProgram();

		// Noise
		modelview_matrix_id = glGetUniformLocation(program_id, (const GLchar *) "modelview_matrix");
		projection_matrix_id = glGetUniformLocation(program_id, (const GLchar *) "projection_matrix");
		offset_id = glGetUniformLocation(program_id, (const GLchar *) "offset");
		mode_id = glGetUniformLocation(program_id, (const GLchar *) "mode");

		// EarthMap
		texture_id = glGetUniformLocation(program_id, (const GLchar *) "textureId");

		// Waving
		wt = glGetUniformLocation(program_id, (const GLchar *)     "waveTime"  );
		ww = glGetUniformLocation(program_id, (const GLchar *)   "waveWidth" );
		wh = glGetUniformLocation(program_id, (const GLchar *) "waveHeight");
		texture_id2 = glGetUniformLocation(program_id, (const GLchar *) "colorMap");

		// Toon
		lightDir = glGetUniformLocation(program_id, (const GLchar *) "lightdir");

		//assert(modelview_matrix_id != -1);
		//assert(projection_matrix_id != -1);
		//assert(mode_id != -1);
		//assert(offset_id != -1);

		glUniform1i(mode_id, mode);
		ShaderSupport::CheckGLErrors(string("InitialzeUniforms"));
	}

	virtual void InitializeMappings()
	{
	}

	void SetModelviewMatrix(const glm::mat4 & mv)
	{
		glUniformMatrix4fv(modelview_matrix_id, 1, GL_FALSE, glm::value_ptr(mv));
		ShaderSupport::CheckGLErrors(string("SetMVP"));
	}

	void SetOffset(float offset)
	{
		glUniform1f(offset_id, offset);
	}

	void SetTexture(GLuint texId)
	{
		glUniform1i(texture_id, texId);
	}

	void SetTexture2(GLuint texId)
	{
		glUniform1i(texture_id2, texId);
	}

	void IncrementMode()
	{
		UseProgram();
		mode = (mode + 1) % 4;
		glUniform1i(mode_id, mode);
		glUseProgram(0);
	}

	void SetProjectionMatrix(const glm::mat4 & p)
	{
		glUniformMatrix4fv(projection_matrix_id, 1, GL_FALSE, glm::value_ptr(p));
		ShaderSupport::CheckGLErrors(string("SetProjectionMatrix"));
	}

};

bool goingUp = true;      // For Waving Shader, used as a guide for a sine wave
float stepNumber = 0.0f; // Used for Waving Shader, used as the amount of time passed (sine wave of time)

// Shader instances
BasicShader basic_shader1;  // Noise
BasicShader basic_shader2;  // Wave
BasicShader basic_shader3;  // Earth
BasicShader basic_shader4;  // Toon
BasicShader basic_shader5;  // Phong

int main(int argc, char** argv) 
{
	// User supplies number of targets and seed
	if(argc == 3)
	{
		srand((int)argv[2]);
		totalBallsOnField = (int) argv[1];
		setupBalls(totalBallsOnField);
		Ball::numBallsLeft = totalBallsOnField;
		startingTime = 30 + totalBallsOnField * 2;
	}
	// User supplies number of targets
	else if(argc == 2)
	{
		srand(0);
		totalBallsOnField = (int) argv[1];
		setupBalls(totalBallsOnField);
		Ball::numBallsLeft = totalBallsOnField;
		startingTime = 30 + totalBallsOnField * 2;
	}
	// User supplies nothing
	else
	{
		srand(70);
		//totalBallsOnField = 40;
		totalBallsOnField = 1;
		Ball::numBallsLeft = totalBallsOnField;
		setupBalls(totalBallsOnField);
		startingTime = 30 + totalBallsOnField * 2;
	}

	//Initialize GLUT
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH | GLUT_MULTISAMPLE);
	glutInitWindowSize(window_width, window_height);

	//Create the window
	glutCreateWindow("Moshball");

	glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_CONTINUE_EXECUTION);
	glutFullScreen();

	// Set up glew
	if (glewInit() != GLEW_OK)
		throw string("GLEW failed to intialize");

	initRendering();
	
	//Set handler functions
	glutDisplayFunc(drawScene);
	glutKeyboardFunc(handleKeypress);
	glutSpecialFunc(handleSpecialKeypress);
	glutReshapeFunc(handleResize);
	glutMouseFunc(mouseButton);
	glutMotionFunc(mouseMotion);
	glutCloseFunc(CloseFunc);

	//Calls function that updates ships
	glutTimerFunc(25, updateBall, 0);

	glutMainLoop();

	CleanMemory();

	return 0;
}

void CloseFunc()
{
	glutLeaveMainLoop();
}

void CloseFunc(int value)
{
	glutLeaveMainLoop();
}

void CleanMemory()
{
	// Delete Shaders
	basic_shader1.TakeDown();
	basic_shader2.TakeDown();
	basic_shader3.TakeDown();
	basic_shader4.TakeDown();
	basic_shader5.TakeDown();

	// Delete font
	our_font.clean();

	// Delete textures
	glDeleteTextures(NUM_REPLAY_FRAMES, Instant_Replay_Display);
	glDeleteTextures(NUM_REPLAY_FRAMES, Instant_Replay_Insert);

	glDeleteTextures(1, &_ad[0]);
	glDeleteTextures(1, &earthMap[0]);
	glDeleteTextures(6, _skybox);
	glDeleteTextures(6, _city);
	glDeleteTextures(6, _morning);

	// Delete pointer arrays
	delete [] Instant_Replay_Display;
	delete [] Instant_Replay_Insert;

	// Delete FBO
	glDeleteFramebuffers(1, &jumbotronObj.fbo);
	glDeleteRenderbuffers(1, &jumbotronObj.fbo_depth);
	glDeleteTextures(1, &jumbotronObj.fbo_texture);

	// Delete display lists
	GLuint display_lists[] = { DisplayListIndex, Ball::index, firework::index, activeStands[0].index, Person::index, NULL };
	GLuint * p = display_lists;
	while (*p != NULL)
	{
		if (*p != GL_INVALID_VALUE)
			glDeleteLists(*p, NUM_DISPLAY_LISTS);
		p++;
	}
}

void initRendering() {
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glEnable(GL_NORMALIZE);
	glEnable(GL_MULTISAMPLE);
	glEnable(GL_SCISSOR_TEST);

	glEnable(GL_DEPTH_TEST);                           // Enable the depth buffer
	glDepthFunc(GL_LEQUAL);                            // Set our depth function to overwrite if new value less than or equal to current value
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Ask for nicest perspective correction

	// Intialize viewing vector
	lookingAt[0] = 0;
	lookingAt[1] = -1;

	// Intialize window and jumbotron dimensions
	window_width = glutGet(GLUT_WINDOW_WIDTH);
	window_height = glutGet(GLUT_WINDOW_HEIGHT);

	jumbotron_width = window_width / 10.0f;
	jumbotron_height = window_height / 10.0f;

	// Initialize meshes
	mesh.ResizeMesh(25, 25, PlanarMesh::Regular);
	theFloor.ResizeMesh(250, 250, PlanarMesh::Regular);
	jumbotronMesh.ResizeMesh(25, 25, PlanarMesh::Regular);
	
	// Adjust jumbotron mesh coordinates for waving shader
	jumbotronMesh.AdjustOutArray((2 * jumbotron_width), (2 * jumbotron_height));

	// Initialize global viewing angle
	degreesLookingAt = 0;

	// Intialize display lists
	DisplayListIndex = glGenLists(NUM_DISPLAY_LISTS);
	InitDisplayLists();
	
	// Initialize all DevIL functionality
	ilInit();
	iluInit();
	ilutInit();
	ilutRenderer(ILUT_OPENGL);	// Tell DevIL that we're using OpenGL for our rendering

	// Initialize Skybox and Font
	LoadSkyBox();
	our_font.init("c:\\windows\\fonts\\arial.ttf", 36);

	// Set up instant replay
	glGenTextures(NUM_REPLAY_FRAMES, Instant_Replay_Insert);
	glGenTextures(NUM_REPLAY_FRAMES, Instant_Replay_Display);

	// Initialize shaders
	basic_shader1.InitializeShader(string("basic.vert"), string("basic.frag"));
	basic_shader2.InitializeShader(string("shader2.vert"), string("shader2.frag"));
	basic_shader3.InitializeShader(string("shader3.vert"), string("shader3.frag"));
	basic_shader4.InitializeShader(string("shader4.vert"), string("shader4.frag"));
	basic_shader5.InitializeShader(string("shader5.vert"), string("shader5.frag"));

	//Stands activation
	activeStands.push_back(Stands(10, Vector3(0, 0, -(HALF_MILE + WALL_HEIGHT * 6)), HALF_MILE + (HALF_MILE / 1.5), WALL_HEIGHT));

	// Create the Crowd
	for(int i = 0; i < activeStands.size(); i++)
	{
		createCrowd(i);
	}

	// Initialize jumbotron
	jumbotronObj = Jumbotron(1,1,1);
}

void InitDisplayLists()
{
	Ball::setupDisplayList();
	firework::setupDisplayList();
	Person::setupDisplayList();

	DrawHole();
	DrawColumns();
	DrawMesh();
	DrawFloor();
	DrawPlayer();
	DrawTexturedTargets();
}

void DrawHole()
{
	glNewList(DisplayListIndex + DRAW_HOLE, GL_COMPILE);
		glDisable(GL_LIGHTING);

		glPushMatrix();
			glTranslateVector(hole);
			glTranslated(0, -BALL_RADIUS, 0);
			glRotated(-90, 1, 0, 0);
			glColor4f(0, 0, 0, 1);
			glutSolidCylinder(BALL_RADIUS, 10, 30, 30);
		glPopMatrix();

		glEnable(GL_LIGHTING);
	glEndList();
}

// Create the jumbotron by redrawing the scene
// texId defaults to 0 except for instant replay
void ScreenToFBO(int texId)
{
	jumbotronObj.Bind(texId, jumbotronObj.fbo);
	jumbotronObj.Draw();
	jumbotronObj.Unbind();
}

// Place jumbotron
void FBOToScreen(bool jumbotron, bool copy)
{
	glEnable(GL_TEXTURE_2D);
    glDisable(GL_LIGHTING);
	glEnable(GL_DEPTH_TEST);

	glPushMatrix();				
		glColor4d(1,1,1,1);

		// Only increment instant replay index if the scene is being put on the screen, not the jumbotron
		if (!gameWon && !jumbotron) 
		{
			insertIndex = (insertIndex + 1) % NUM_REPLAY_FRAMES;
			displayIndex = insertIndex;
		}

		// Display Ad or FBO
		int adTime = (int)timer.TotalTime();
		if (adTime % 60 >= 15 && adTime % 60 < 20) 
			glBindTexture(GL_TEXTURE_2D, _ad[0]);
		else if (Play_Instant_Replay)
		{
			// If the current frame has a value of 0, don't display it (no texture)
			do
			{
				displayIndex = (displayIndex + 1) % NUM_REPLAY_FRAMES;
				if (displayIndex == 0) initializing = false;
			} while (Instant_Replay_Display[displayIndex] == 0);

			// If you get back to the starting frame, turn replay off
			if (displayIndex == (startNumber - 1 + NUM_REPLAY_FRAMES) % NUM_REPLAY_FRAMES)
			{
				//UpdateInstantReplayDisplayArray();
				Play_Instant_Replay = false;
			}

			// Bind to replay texture
			glBindTexture(GL_TEXTURE_2D, Instant_Replay_Display[displayIndex]);
		}
		else 
		{
			if (Waving_Jumbotron)
			{
				basic_shader2.UseProgram();
				glActiveTexture(GL_TEXTURE0);
				basic_shader2.SetTexture2(0);

				glUniform1f(basic_shader2.wt, stepNumber);
				glUniform1f(basic_shader2.ww, 30.0f);
				glUniform1f(basic_shader2.wh, 10.0f);

				// Sine function simulation
				if (goingUp)
				{
					stepNumber += 0.05f;
					if (stepNumber == 1.0f) 
					{
						stepNumber = 0.95f;
						goingUp = false;
					}
				}
				else
				{
					stepNumber -= 0.05f;
					if (stepNumber == 0.0f) 
					{
						stepNumber = 0.05f;
						goingUp = true;
					}
				}
			}
			glBindTexture(GL_TEXTURE_2D, jumbotronObj.fbo_texture);
		}

		// Place FBO on jumbotron
		glPushMatrix();
			glTranslated(0, 250, -HALF_MILE);

			// Attempt to put draw mesh instead of quad for shader
			if (!JumbotronMesh)
				jumbotronMesh.Draw(PlanarMesh::OutArray, false);
			else
			{
				glBegin(GL_QUADS);
					glTexCoord2f(0.0f, 0.0f);  
					glVertex3f(-jumbotron_width, -jumbotron_height, 0.0f); // The bottom left corner  
  
					glTexCoord2f(0.0f, 1.0f);  
					glVertex3f(-jumbotron_width,  jumbotron_height, 0.0f);  // The top left corner  
  
					glTexCoord2f(1.0f, 1.0f);  
					glVertex3f( jumbotron_width,  jumbotron_height, 0.0f);   // The top right corner  
  
					glTexCoord2f(1.0f, 0.0f);  
					glVertex3f( jumbotron_width, -jumbotron_height, 0.0f);  // The bottom right corner 
				glEnd();
			}
		glPopMatrix();
		glPushMatrix();
			glTranslated(-HALF_MILE, 250, 0);
			glRotated(90, 0, 1, 0);
			if (!JumbotronMesh)
				jumbotronMesh.Draw(PlanarMesh::OutArray, false);
			else
			{
				glBegin(GL_QUADS);
					glTexCoord2f(0.0f, 0.0f);  
					glVertex3f(-jumbotron_width, -jumbotron_height, 0.0f); // The bottom left corner  
  
					glTexCoord2f(0.0f, 1.0f);  
					glVertex3f(-jumbotron_width,  jumbotron_height, 0.0f);  // The top left corner  
  
					glTexCoord2f(1.0f, 1.0f);  
					glVertex3f( jumbotron_width,  jumbotron_height, 0.0f);   // The top right corner  
  
					glTexCoord2f(1.0f, 0.0f);  
					glVertex3f( jumbotron_width, -jumbotron_height, 0.0f);  // The bottom right corner 
				glEnd();
			}
		glPopMatrix();
		glPushMatrix();
			glTranslated(HALF_MILE, 250, 0);
			glRotated(-90, 0, 1, 0);
			if (!JumbotronMesh)
				jumbotronMesh.Draw(PlanarMesh::OutArray, false);
			else
			{
				glBegin(GL_QUADS);
					glTexCoord2f(0.0f, 0.0f);  
					glVertex3f(-jumbotron_width, -jumbotron_height, 0.0f); // The bottom left corner  
  
					glTexCoord2f(0.0f, 1.0f);  
					glVertex3f(-jumbotron_width,  jumbotron_height, 0.0f);  // The top left corner  
  
					glTexCoord2f(1.0f, 1.0f);  
					glVertex3f( jumbotron_width,  jumbotron_height, 0.0f);   // The top right corner  
  
					glTexCoord2f(1.0f, 0.0f);  
					glVertex3f( jumbotron_width, -jumbotron_height, 0.0f);  // The bottom right corner 
				glEnd();
			}
		glPopMatrix();
		glPushMatrix();
			glTranslated(0, 250, HALF_MILE);
			glRotated(180, 0, 1, 0);
			if (!JumbotronMesh)
				jumbotronMesh.Draw(PlanarMesh::OutArray, false);
			else
			{
				glBegin(GL_QUADS);
					glTexCoord2f(0.0f, 0.0f);  
					glVertex3f(-jumbotron_width, -jumbotron_height, 0.0f); // The bottom left corner  
  
					glTexCoord2f(0.0f, 1.0f);  
					glVertex3f(-jumbotron_width,  jumbotron_height, 0.0f);  // The top left corner  
  
					glTexCoord2f(1.0f, 1.0f);  
					glVertex3f( jumbotron_width,  jumbotron_height, 0.0f);   // The top right corner  
  
					glTexCoord2f(1.0f, 0.0f);  
					glVertex3f( jumbotron_width, -jumbotron_height, 0.0f);  // The bottom right corner 
				glEnd();
			}
		glPopMatrix();
	glPopMatrix();

	// Go back to the screen
	glBindTexture(GL_TEXTURE_2D, 0); 
	glUseProgram(0);

	glDisable(GL_TEXTURE_2D);
	glEnable(GL_LIGHTING);
}

// Copy scene for instant replay
void CopyScene()
{
	// Copy texture from frame buffer
	/*glEnable(GL_LIGHTING);
	glEnable(GL_DEPTH_TEST);
	ScreenToFBO(Instant_Replay_Insert[insertIndex]);
	glDisable(GL_LIGHTING);
	glDisable(GL_DEPTH_TEST);*/
	glEnable(GL_TEXTURE_2D);
	glColor4f(1,1,1,1);

	// Don't copy to display array if you're current watching the replay
	if (!Play_Instant_Replay)
	{
		glBindTexture(GL_TEXTURE_2D, Instant_Replay_Display[displayIndex]);

		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);  
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);  
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);  
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

		// Allocate memory for texture
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, window_width, window_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);

		// Bind frame buffer
		glBindFramebuffer(GL_DRAW_FRAMEBUFFER, jumbotronObj.fbo);

		// Copy texture from frame buffer
		glCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, window_width, window_height );
	}

	// Do the cleanup
	glBindTexture( GL_TEXTURE_2D, 0 );
	glDisable(GL_TEXTURE_2D);
	glBindFramebuffer( GL_DRAW_FRAMEBUFFER, 0);
}

//void UpdateInstantReplayDisplayArray()
//{
//	for (int i = 0; i < NUM_REPLAY_FRAMES; i++)
//	{
//		//glDeleteTextures(1, &Instant_Replay_Display[i]);
//
//		//glGenTextures(1, &Instant_Replay_Display[i]);
//		glBindTexture(GL_TEXTURE_2D, Instant_Replay_Display[i]);
//
//		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);  
//		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);  
//		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);  
//		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
//
//		// Allocate memory for texture
//		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, window_width, window_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
//
//		// Bind to fbo
//		glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
//
//		// Put the wanted texture in the fbo
//		glFramebufferTexture2D( GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, Instant_Replay_Insert[i], 0 );
//
//		// Copy texture from frame buffer
//		glCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, window_width, window_height );
//
//		// Clean up
//		glBindTexture( GL_TEXTURE_2D, 0 );
//		glBindFramebuffer( GL_DRAW_FRAMEBUFFER, 0);
//
//	}
//}

//Draws the 3D scene
void drawScene() 
{
	glUseProgram(0);

	glViewport(0, 0, window_width, window_height);
	glScissor(0, 0, window_width, window_height);

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	// Set up projection matrix
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(45.0, (double)window_width / (double)window_height, 1.0, 4*ONE_MILE);
	
	// Set up modelview matrix
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	//Allows you to turn on wireframe and smooth shading
	shadeModel ? glShadeModel(GL_SMOOTH) : glShadeModel(GL_FLAT);
	glPolygonMode(GL_FRONT_AND_BACK, wireFrame ? GL_LINE : GL_FILL);

	//Test: Wall Connections
	//gluLookAt(0, ONE_MILE, -ONE_MILE, 0, 0, 0, 0, 1, 0);

	camera = Vector3(yourBall.getPosition().x, 0, yourBall.getPosition().z);

	// Determine if you are above or below the floor
	if (yourBall.aboveFloor)
	{
		gluLookAt(yourBall.getPosition().x, 0, yourBall.getPosition().z, 
		      yourBall.getPosition().x + lookingAt[0], 0, yourBall.getPosition().z + lookingAt[1], 
			  0, 1, 0);
	}
	else
	{
		gluLookAt(yourBall.getPosition().x, -2*BALL_RADIUS, yourBall.getPosition().z, 
		      yourBall.getPosition().x + lookingAt[0], -2*BALL_RADIUS, yourBall.getPosition().z + lookingAt[1], 
			  0, 1, 0);
	}

	// Global Light Position
	global_position[0] = yourBall.getPosition().x;
	global_position[1] = yourBall.getPosition().y;
	global_position[2] = yourBall.getPosition().z;
	glLightfv(GL_LIGHT0, GL_POSITION, global_position);

	// Render scene to jumbotron
	ScreenToFBO();

	// Implement fog
	if (Fog_Is_Active)
	{
		glEnable(GL_FOG);
		glFogi(GL_FOG_MODE, GL_LINEAR);

		glFogf(GL_FOG_START, HALF_MILE/2);
		glFogf(GL_FOG_END, HALF_MILE);
		glFogf(GL_FOG_DENSITY, 0.35f);
		glHint(GL_FOG_HINT, GL_NICEST);
	}

	DrawSkyBox(REGULAR);
	DrawSkyBox(REFLECTION);
	
	DrawWalls();
	glCallList(DisplayListIndex + DRAW_COLUMNS);

	if (Hole_Is_Active)
		glCallList(DisplayListIndex + DRAW_HOLE);

	drawBalls(REGULAR);

	// Draw reflective scene
	glPushMatrix();
		glScaled(1, -1, 1);
		glTranslated(0, 2*BALL_RADIUS, 0);
		DrawWalls();
		glCallList(DisplayListIndex + DRAW_COLUMNS);
		if (Hole_Is_Active) 
			glCallList(DisplayListIndex + DRAW_HOLE);
		drawBalls(REFLECTION);

		//Draw Stands
		for(int i = 0; i < activeStands.size(); i++)
		{
			glPushMatrix();
				glTranslateVector(activeStands[i].getPostition());
				activeStands[i].Draw();
			glPopMatrix();
		}

		FBOToScreen(true); // Draw reflected jumbotron
	glPopMatrix();

	//Draw Fireworks
	for(int i = 0; i < activeFireworks.size(); i++)
	{
		glPushMatrix();
			glTranslateVector(activeFireworks[i].getPosition());
			if(!activeFireworks[i].draw())
			{
				activeFireworks.erase(activeFireworks.begin() + i);
			}
		glPopMatrix();
	}

	//Draw Stands
	for(int i = 0; i < activeStands.size(); i++)
	{
		glPushMatrix();
			glTranslateVector(activeStands[i].getPostition());
			activeStands[i].Draw();
		glPopMatrix();
	}


	glCallList(DisplayListIndex + DRAW_FLOOR);

	FBOToScreen(); // Draw jumbotron
	
	CopyScene(); // Copy scene into replay array

	// Render timers for the targets
	for (int i = 0; i < ballsOnField.size(); i++)
	{
		glPushMatrix();
		glTranslateVector(ballsOnField[i].getPosition());
		if (ballsOnField[i].ballHit) ballsOnField[i].renderTimer();
		glPopMatrix();
	}

	glDisable(GL_FOG);

	// Draw cross section, timer, and targets remaining
	drawPlus();
	DrawFont();

	// See if you've won
	if (Ball::numBallsLeft == 0)
	{
		gameWon = true;
		//Play_Instant_Replay = true;
		DisplayWinScreen();
	}

	// Draw God-View
	glPushMatrix();
		glDisable(GL_LIGHTING);

		glViewport(50, 50, GOD_VIEW_SIDE, GOD_VIEW_SIDE);
		glScissor (50, 50, GOD_VIEW_SIDE, GOD_VIEW_SIDE);

		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		gluPerspective(45.0, 1, 1, 2*ONE_MILE);

		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();

		gluLookAt(0, 3*HALF_MILE, 0.000001, 0, 0, 0, 0, 1, 0);

		glEnable(GL_LIGHTING);

		drawBalls(REGULAR, true);
		DrawWalls(true);
	glPopMatrix();
		
	glutSwapBuffers();
}

void DisplayWinScreen()
{
	//glutTimerFunc(5000, CloseFunc, 0);

	glPushMatrix();
			glDisable(GL_LIGHTING);
			glEnable(GL_BLEND);
			glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		
			glLoadIdentity();
			glTranslated(0, 0, -5);

			double winColor = 0;

			glColor4d(winColor, winColor, winColor, 0.7);
			glScaled(5,5,5);

			// Cast black shadow over scene
			glBegin(GL_TRIANGLES);
				glVertex2d(-1,  1);
				glVertex2d(-1, -1);
				glVertex2d( 1, -1);

				glVertex2d(-1,  1);
				glVertex2d( 1,  1);
				glVertex2d( 1, -1);
			glEnd();

			glDisable(GL_BLEND);

			glColor3d(1,1,1);
			glLoadIdentity();

			glTranslated(-.5, -.04, -2);
			glScaled(TIMER_SCALE, TIMER_SCALE, TIMER_SCALE);
			freetype::print(our_font, 0, 0, "You Won!");

			glEnable(GL_LIGHTING);
		glPopMatrix();
}

void DrawFloor()
{
	glNewList(DisplayListIndex + DRAW_FLOOR, GL_COMPILE);
		glDepthMask(GL_FALSE);
		glDisable(GL_LIGHTING);
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

		glPushMatrix();
			glTranslated(0, -BALL_RADIUS, 0);
			glColor4f(floorColor[0], floorColor[1], floorColor[2], floorColor[3]);
			glScaled(ONE_MILE, 0, ONE_MILE);
			glRotated(-90, 1, 0, 0);
			theFloor.Draw();
		glPopMatrix();

		glDepthMask(GL_TRUE);
		glEnable(GL_LIGHTING);
		glDisable(GL_BLEND);
	glEndList();
}

void LoadSkyBox()
{
	// Default skybox
	ILstring imageFileName = (ILstring) "skybox_texture_top.jpg";
	_skybox[TOP] = ilutGLLoadImage(imageFileName);

	imageFileName = (ILstring) "skybox_texture_left.jpg";
	_skybox[LEFT] = ilutGLLoadImage(imageFileName);

	imageFileName = (ILstring) "skybox_texture_right.jpg";
	_skybox[RIGHT] = ilutGLLoadImage(imageFileName);

	imageFileName = (ILstring) "skybox_texture_front.jpg";
	_skybox[FRONT] = ilutGLLoadImage(imageFileName);

	imageFileName = (ILstring) "skybox_texture_back.jpg";
	_skybox[BACK] = ilutGLLoadImage(imageFileName);

	imageFileName = (ILstring) "skybox_texture_bottom.jpg";
	_skybox[BOTTOM] = ilutGLLoadImage(imageFileName);

	// City Skybox
	imageFileName = (ILstring) "city_negative_x.png";
	_city[LEFT] = ilutGLLoadImage(imageFileName);

	imageFileName = (ILstring) "city_positive_x.png";
	_city[RIGHT] = ilutGLLoadImage(imageFileName);

	imageFileName = (ILstring) "city_positive_z.png";
	_city[FRONT] = ilutGLLoadImage(imageFileName);

	imageFileName = (ILstring) "city_negative_z.png";
	_city[BACK] = ilutGLLoadImage(imageFileName);

	// Morning
	imageFileName = (ILstring) "skymorning_negative_x.jpg";
	_morning[LEFT] = ilutGLLoadImage(imageFileName);

	imageFileName = (ILstring) "skymorning_positive_x.jpg";
	_morning[RIGHT] = ilutGLLoadImage(imageFileName);

	imageFileName = (ILstring) "skymorning_positive_z.jpg";
	_morning[FRONT] = ilutGLLoadImage(imageFileName);

	imageFileName = (ILstring) "skymorning_negative_z.jpg";
	_morning[BACK] = ilutGLLoadImage(imageFileName);

	// OpenGL ad
	imageFileName = (ILstring) "opengl_logo.png";
	_ad[0] = ilutGLLoadImage(imageFileName);

	// EarthMap
	imageFileName = (ILstring) "EarthMap.jpg";
	earthMap[0] = ilutGLLoadImage(imageFileName);
}

void DrawColumns()
{
	glNewList(DisplayListIndex + DRAW_COLUMNS, GL_COMPILE);
		glPushMatrix();
			glTranslated(0, 0, -HALF_MILE - 50);
			glRotated(-90, 1, 0, 0);
			glutSolidCylinder(25, 250, 40, 40);
		glPopMatrix();
		glPushMatrix();
			glTranslated(HALF_MILE + 50, 0, 0);
			glRotated(-90, 1, 0, 0);
			glutSolidCylinder(25, 250, 40, 40);
		glPopMatrix();
		glPushMatrix();
			glTranslated(0, 0, HALF_MILE + 50);
			glRotated(-90, 1, 0, 0);
			glutSolidCylinder(25, 250, 40, 40);
		glPopMatrix();
		glPushMatrix();
			glTranslated(-HALF_MILE - 50, 0, 0);
			glRotated(-90, 1, 0, 0);
			glutSolidCylinder(25, 250, 40, 40);
		glPopMatrix();
	glEndList();
}

void DrawFont()
{
	// Get the total amount of play time
	double time = timer.TotalTime();

	std::ostringstream s;
	s << time;
	
	std::stringstream ballsLeft;
	ballsLeft << Ball::numBallsLeft;

	// Get two decimal places in the time
	string totalTime = "Time: " + s.str().substr(0, s.str().find_first_of('.') + 3);
	string TargetsLeft = "Targets: " + ballsLeft.str();

	glPushMatrix();
		glLoadIdentity();
		glTranslated(2, -3, -10);
		glColor3d(1, 1, 1);

		glPushMatrix();
			glScaled(TIMER_SCALE, TIMER_SCALE, TIMER_SCALE);
			freetype::print(our_font, 0, 0, totalTime.c_str());
		glPopMatrix();

		glTranslated(1.6, 0, 0);
		glScaled(TIMER_SCALE, TIMER_SCALE, TIMER_SCALE);
		freetype::print(our_font, 0, 0, TargetsLeft.c_str());
	glPopMatrix();
}

void DrawWalls( bool GodView )
{
	if (!GodView) 
	{
		// Add Phong Shading to the targets
		if (PhongShading)
		{
			basic_shader4.UseProgram();
			//lightWall(wallColor);
		}
		lightWall(wallColor);
	}
	else 
	{
		glDisable(GL_LIGHTING);
		glColor3f(wallColor[0], wallColor[1], wallColor[2]);
	}

	glPushMatrix();
		glTranslated(0, 0, -HALF_MILE);
		if (GodView) glRotated(-90, 1, 0, 0);
		glScaled(ONE_MILE, 2*BALL_RADIUS, 1);	
		glCallList(DisplayListIndex + DRAW_WALL);
	glPopMatrix();
	glPushMatrix();
		glTranslated(HALF_MILE, 0, 0);
		glRotated(-90, 0, 1, 0);
		if (GodView) glRotated(-90, 1, 0, 0);
		glScaled(ONE_MILE, 2*BALL_RADIUS, 1);
		glCallList(DisplayListIndex + DRAW_WALL);
	glPopMatrix();
	glPushMatrix();
		glTranslated(0, 0, HALF_MILE);
		glRotated(180, 0, 1, 0);
		if (GodView) glRotated(-90, 1, 0, 0);
		glScaled(ONE_MILE, 2*BALL_RADIUS, 1);
		glCallList(DisplayListIndex + DRAW_WALL);
	glPopMatrix();
	glPushMatrix();
		glTranslated(-HALF_MILE, 0, 0);
		glRotated(90, 0, 1, 0);
		if (GodView) glRotated(-90, 1, 0, 0);
		glScaled(ONE_MILE, 2*BALL_RADIUS, 1);
		glCallList(DisplayListIndex + DRAW_WALL);
	glPopMatrix();

	glUseProgram(0);
	glEnable(GL_LIGHTING);
}

void DrawMesh()
{
	glNewList(DisplayListIndex + DRAW_WALL, GL_COMPILE);
		mesh.Draw(PlanarMesh::InArray, false);
	glEndList();
}

void DrawSkyBox(int sceneType)
{
	glDisable(GL_DEPTH_TEST);
	glDisable(GL_LIGHTING);
	glEnable(GL_TEXTURE_2D);

	glPushMatrix();
		glLoadIdentity();		

		gluLookAt(yourBall.getPosition().x, 0, yourBall.getPosition().z, 
		          yourBall.getPosition().x + lookingAt[0], 0, yourBall.getPosition().z + lookingAt[1], 
			      0, 1, 0);

		glColor4f(1,1,1,1);

		glTranslated(yourBall.getPosition().x, 0, yourBall.getPosition().z);

		if (rotateSkybox) glRotated(skyboxRotationAngle, 0, 1, 0);

		if (sceneType == REFLECTION) glScaled(1, -1, 1);

		// Bind the BACK texture of the sky map to the BACK side of the cube  
		if      (skybox_num == 0) glBindTexture(GL_TEXTURE_2D, _skybox[BACK]);
		else if (skybox_num == 1) glBindTexture(GL_TEXTURE_2D, _city[BACK]);
		else if (skybox_num == 2) glBindTexture(GL_TEXTURE_2D, _morning[BACK]);

		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

		glBegin(GL_QUADS);     										    
		   glTexCoord2f(0.0f, 0.5f); glVertex3f( 2,  0, 2);
		   glTexCoord2f(0.0f, 1.0f); glVertex3f( 2,  2, 2);
		   glTexCoord2f(1.0f, 1.0f); glVertex3f(-2,  2, 2); 
		   glTexCoord2f(1.0f, 0.5f); glVertex3f(-2,  0, 2);  
	    glEnd();

		// Bind the FRONT texture of the sky map to the FRONT side of the cube 
		if      (skybox_num == 0) glBindTexture(GL_TEXTURE_2D, _skybox[FRONT]);
		else if (skybox_num == 1) glBindTexture(GL_TEXTURE_2D, _city[FRONT]);
		else if (skybox_num == 2) glBindTexture(GL_TEXTURE_2D, _morning[FRONT]);

		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	      				    
	    glBegin(GL_QUADS);     										    
		   glTexCoord2f(0.0f, 0.5f); glVertex3f(-2,  0, -2);
		   glTexCoord2f(0.0f, 1.0f); glVertex3f(-2,  2, -2);
		   glTexCoord2f(1.0f, 1.0f); glVertex3f( 2,  2, -2); 
		   glTexCoord2f(1.0f, 0.5f); glVertex3f( 2,  0, -2);  
	    glEnd();

		// Bind the BOTTOM texture of the sky map to the BOTTOM side of the cube
		/*glBindTexture(GL_TEXTURE_2D, _skybox[BOTTOM]);
		
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	    
	    glBegin(GL_QUADS);        									    
		   glTexCoord2f(0.0f, 0.5f); glVertex3f(-2,  0,  2);
		   glTexCoord2f(0.0f, 1.0f); glVertex3f(-2, -2, -2);
		   glTexCoord2f(1.0f, 1.0f); glVertex3f( 2, -2, -2); 
		   glTexCoord2f(1.0f, 0.5f); glVertex3f( 2,  0,  2); 
	    glEnd();*/

		// Bind the TOP texture of the sky map to the TOP side of the cube
		/*glBindTexture(GL_TEXTURE_2D, _skybox[TOP]); 

		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	     
	    glBegin(GL_QUADS);        
		   glTexCoord2f(0.0f, 0.5f); glVertex3f(-2, 0, -2);
		   glTexCoord2f(0.0f, 1.0f); glVertex3f(-2, 2,  2);
		   glTexCoord2f(1.0f, 1.0f); glVertex3f( 2, 2,  2); 
		   glTexCoord2f(1.0f, 0.5f); glVertex3f( 2, 0, -2);  
	    glEnd(); */ 

		// Bind the LEFT texture of the sky map to the LEFT side of the cube
		if      (skybox_num == 0) glBindTexture(GL_TEXTURE_2D, _skybox[LEFT]);  
		else if (skybox_num == 1) glBindTexture(GL_TEXTURE_2D, _city[LEFT]);  
		else if (skybox_num == 2) glBindTexture(GL_TEXTURE_2D, _morning[LEFT]);  

		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	    
	    glBegin(GL_QUADS);        
		   glTexCoord2f(0.0f, 0.5f); glVertex3f(-2,  0,  2);
		   glTexCoord2f(0.0f, 1.0f); glVertex3f(-2,  2,  2);
		   glTexCoord2f(1.0f, 1.0f); glVertex3f(-2,  2, -2); 
		   glTexCoord2f(1.0f, 0.5f); glVertex3f(-2,  0, -2);       
	    glEnd();

		// Bind the RIGHT texture of the sky map to the RIGHT side of the cube
		if      (skybox_num == 0) glBindTexture(GL_TEXTURE_2D, _skybox[RIGHT]);  
		else if (skybox_num == 1) glBindTexture(GL_TEXTURE_2D, _city[RIGHT]); 
		else if (skybox_num == 2) glBindTexture(GL_TEXTURE_2D, _morning[RIGHT]); 

		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	    
	    glBegin(GL_QUADS);     
		   glTexCoord2f(0.0f, 0.5f); glVertex3f(2,  0, -2);
		   glTexCoord2f(0.0f, 1.0f); glVertex3f(2,  2, -2);
		   glTexCoord2f(1.0f, 1.0f); glVertex3f(2,  2,  2); 
		   glTexCoord2f(1.0f, 0.5f); glVertex3f(2,  0,  2); 
	    glEnd();
	glPopMatrix();

	glEnable(GL_DEPTH_TEST);
	glEnable(GL_LIGHTING);
	glDisable(GL_TEXTURE_2D);
}

void handleKeypress(unsigned char key, int x, int y) 
{
	switch (key) 
	{
		case 'x':
		case 27: // Quit
			glutLeaveMainLoop();
			break;
		case 'p': // Pause on/off
			paused = !paused; 
			timer.TimeSinceLastCall();
			if (paused)
			{
				pause_start = glutGet(GLUT_ELAPSED_TIME);
			}
			else
			{
				paused_time += glutGet(GLUT_ELAPSED_TIME) - pause_start;
			}
			break;
		case 'w': // Wireframe on/off
			wireFrame = !wireFrame;
			break;
		case 's': // Shade Model (smooth vs flat)
			shadeModel = !shadeModel;
			break;
		case 'n': // Normals on/off
			normals = !normals;
			break;
		case 'b': // Billiards Mode on/off
			billardsOn = !billardsOn;
			if(billardsOn)
			{
				cout << "billards on" << endl;
				for(int i = 0; i < ballsOnField.size(); i++)
				{
					ballsOnField[i].setVelocity(yourBall.getVelocity());
				}
			}
			else
			{
				cout << "billards off" << endl;
				for(int i = 0; i < ballsOnField.size(); i++)
				{
					ballsOnField[i].setVelocity(Vector3());
				}
			}
			break;
		case 'S': // Noise shader on/off
			use_shader = !use_shader;
			break;
		case ' ': // Switch skybox
			skybox_num = (skybox_num + 1) % NUM_SKYBOXES;
			break;
		case 'W': // Warp zone on/off
			warp_zone_active = !warp_zone_active;
			break;
		case 'r': // Rotate the skybox
			rotateSkybox = !rotateSkybox;
			break;
		case 'i': // Play instant replay
			if (!gameWon)
				Play_Instant_Replay = !Play_Instant_Replay;
			if (Play_Instant_Replay) 
			{
				displayIndex = insertIndex;
				startNumber = insertIndex;
			}
			break;
		case 'h': // Hole in floor on/off
			Hole_Is_Active = !Hole_Is_Active;
			if(Hole_Is_Active)
			{
				for(int i = 0; i < ballsOnField.size(); i++)
				{
					(RangeRand(0, 10) <=5)?ballsOnField[i].aboveFloor=true:ballsOnField[i].aboveFloor=false;
				}
			}
			else
			{
				for(int i = 0; i < ballsOnField.size(); i++)
				{
					ballsOnField[i].aboveFloor = true;
				}
			}
			break;
		case 'E': // EarthMap shader on/off
			EarthBall = !EarthBall;
			break;
		case 'f': // Fireworks on/off
			fireworksOn = !fireworksOn;
			launchFireworks(0);
			break;
		case 'F': // Fog on/off
			Fog_Is_Active = !Fog_Is_Active;
			break;
		case 'j': // Waving Shader on/off
			Waving_Jumbotron = !Waving_Jumbotron;
			break;
		case 'J': // Turn jumbotron mesh on/off
			JumbotronMesh = !JumbotronMesh;
			break;
		case 'B': // Phong shading on/off
			PhongShading = !PhongShading;
			break;
		case 't': // Toon Shader on/off
			ToonBalls = !ToonBalls;
			break;
	}
	glutPostRedisplay();
}

void handleSpecialKeypress(int key, int x, int y)
{
}

void mouseButton(int button, int state, int x, int y)
{
}

void mouseMotion(int x, int y)
{
}

// Called when the window is resized
void handleResize(int w, int h) {
	glViewport(0, 0, w, h);
	
	window_width = w;
	window_height = h;
	
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(45.0, (double)window_width / (double)window_height, 1.0, 2*ONE_MILE);
}

// Update the timer over each ball
void reduceTimer(int value)
{
	if(ballsOnField[value].decTimer())
		glutTimerFunc(1000, reduceTimer, value);

	glutPostRedisplay();
}

// Initialize targets
void setupBalls(int numBalls)
{
	bool goodPlace;
	float x, y, z;
	
	// Place each ball
	for(int i = 0; i < numBalls; i++)
	{
		goodPlace = true;

		do
		{
			// Obtain a possible position
			x = RangeRand(-(HALF_MILE - 50), HALF_MILE - 50);
			y = 0;
			z = RangeRand(-(HALF_MILE - 50), HALF_MILE - 50);
			
			// Make sure the ball doesn't intersect any other balls that have already been placed
			for (int j = 0; j < i; j++)
			{
				Vector3 distBetweenBalls = Vector3(x - ballsOnField[j].getPosition().x,
					                               y - ballsOnField[j].getPosition().y,
												   z - ballsOnField[j].getPosition().z);
				
				// If the balls overlap, a good position has not been found
				if (distBetweenBalls.magnitude() <= BALL_RADIUS * 2)
				{
					goodPlace = false;
					break;
				}
			}

		} while (!goodPlace);

		// Put the ball in the good position
		ballsOnField.push_back(Ball(Vector3(x, y, z)));
		ballsOnField[i].rollAngle = 0;
	}

	// Place warp zone and black hole
	for(int i = 0; i < 2; i++)
	{
		goodPlace = true;

		do
		{
			// Obtain a possible position
			x = RangeRand(-(HALF_MILE - 50), HALF_MILE - 50);
			y = 0;
			z = RangeRand(-(HALF_MILE - 50), HALF_MILE - 50);
			
			// Make sure they don't intersect any other balls that have already been placed
			for (int j = 0; j < ballsOnField.size(); j++)
			{
				Vector3 distBetweenBalls = Vector3(x - ballsOnField[j].getPosition().x,
					                               y - ballsOnField[j].getPosition().y,
												   z - ballsOnField[j].getPosition().z);
				
				// If the balls overlap, a good position has not been found
				if (distBetweenBalls.magnitude() <= BALL_RADIUS * 2)
				{
					goodPlace = false;
					break;
				}
			}

			// If it doesn't intersect any balls, make sure it doesn't intersect the warpZone
			if (goodPlace && i == 2)
			{
				Vector3 distBetweenBalls = Vector3(x - warpZone.getPosition().x,
					                               y - warpZone.getPosition().y,
												   z - warpZone.getPosition().z);
				
				// If the balls overlap, a good position has not been found
				if (distBetweenBalls.magnitude() <= BALL_RADIUS * 2)
				{
					goodPlace = false;
				}
			}

		} while (!goodPlace);

		// Put the ball in the good position
		if (i == 1) warpZone.setPosition(Vector3(x,y,z));
		else        hole = Vector3(x,y,z);
	}

	Ball::angle = 0; // for warp zone
}

// Throw player into random position on field
void WarpBall()
{
	bool goodPlace;
	float x, y, z;
	
	do
	{
		// Place each ball
		for(int i = 0; i < (int)ballsOnField.size(); i++)
		{
			goodPlace = true;

			// Obtain a possible position
			x = RangeRand(-HALF_MILE + 50, HALF_MILE - 50);
			y = 0;
			z = RangeRand(-HALF_MILE + 50, HALF_MILE - 50);
			
			// Make sure the ball doesn't intersect any other balls that have already been placed
			Vector3 distBetweenBalls = Vector3(x - ballsOnField[i].getPosition().x,
											   y - ballsOnField[i].getPosition().y,
											   z - ballsOnField[i].getPosition().z);

			// If the balls overlap, a good position has not been found, and try again
			if (distBetweenBalls.magnitude() <= BALL_RADIUS * 2)
			{
				goodPlace = false;
				break;
			}
		}
	} while (!goodPlace);

	// Put the ball in the good position
	yourBall.setPosition(Vector3(x, y, z));
}

void lightWall( float * wallColor )
{
	GLfloat	ambient[4]	= { 0.5f, 0.5f, 0.5f, 1.0f };
	GLfloat	diffuse[4]	= { wallColor[0], wallColor[1], wallColor[2], wallColor[3] };
	GLfloat	specular[4]	= { 0.50f , 0.50f , 0.50f , 1.00f };

	glMaterialf( GL_FRONT , GL_SHININESS , 100.0f);
	glMaterialfv(GL_FRONT , GL_AMBIENT   , ambient);
	glMaterialfv(GL_FRONT , GL_DIFFUSE   , diffuse);
	glMaterialfv(GL_FRONT , GL_SPECULAR  , specular);
}

void DrawTexturedTargets()
{
	glNewList(DisplayListIndex + DRAW_GLU_BALL, GL_COMPILE);
		GLUquadricObj *qObj = gluNewQuadric();
		gluQuadricNormals(qObj, GLU_SMOOTH);
		gluQuadricTexture(qObj, GL_TRUE);

		gluSphere(qObj, 50, 40, 40);

		gluDeleteQuadric(qObj);
	glEndList();
}

void drawBalls( int reflection, bool GodView )
{
	glPushMatrix();		
		float ballColor[4];

		for(int i = 0; i < (int) ballsOnField.size(); i++)
		{
			// For GodView, only display targets on your level if hole is active
			if      (!GodView && reflection == REGULAR && !ballsOnField[i].aboveFloor && Hole_Is_Active) continue;
			else if (!GodView && reflection != REGULAR &&  ballsOnField[i].aboveFloor && Hole_Is_Active) continue;

			if (ballsOnField[i].ballHit)
			{
				// Ball is Red
				ballColor[0] = 1.0f;
				ballColor[1] = 0.0f;
				ballColor[2] = 0.0f;
				if (reflection == REGULAR) 
					ballColor[3] = 1.0f;
				else ballColor[3] = 0.5f;
			}
			else
			{
				// Ball is Green
				ballColor[0] = 0.0f;
				ballColor[1] = 1.0f;
				ballColor[2] = 0.0f;
				if (reflection == REGULAR) 
					ballColor[3] = 1.0f;
				else ballColor[3] = 0.5f;
			}

			// Using Noise or EarthMap shader
			if ((use_shader || EarthBall) && !GodView && !ballsOnField[i].ballHit) 
			{
				if (use_shader)	basic_shader1.UseProgram();
				else if (EarthBall)           
				{
					glEnable(GL_TEXTURE_2D);
					basic_shader3.UseProgram();
					glActiveTexture(GL_TEXTURE0);
					basic_shader3.SetTexture(0);
					glBindTexture(GL_TEXTURE_2D, earthMap[0]);
				}

				// Set up Noise shader
				if (use_shader)
				{
					shader_projection.push(glm::perspective((float)45.0, ((float)window_width / (float)window_height), (float)1.0, (float)2*ONE_MILE));

					if (reflection == REGULAR)
					{
						shader_modelview.push(glm::translate(glm::lookAt(glm::vec3(yourBall.getPosition().x, 0.0f, yourBall.getPosition().z), 
														 glm::vec3(yourBall.getPosition().x + lookingAt[0], 0.0f, yourBall.getPosition().z + lookingAt[1]), 
														 glm::vec3(0.0f, 1.0f, 0.0f)), 
														 glm::vec3(ballsOnField[i].getPosition().x, 0, ballsOnField[i].getPosition().z)));
					}
					else
					{
						shader_modelview.push(glm::translate(glm::lookAt(glm::vec3(yourBall.getPosition().x, 0.0f, yourBall.getPosition().z), 
														 glm::vec3(yourBall.getPosition().x + lookingAt[0], 0.0f, yourBall.getPosition().z + lookingAt[1]), 
														 glm::vec3(0.0f, 1.0f, 0.0f)), 
														 glm::vec3(ballsOnField[i].getPosition().x, (float)-2*BALL_RADIUS, ballsOnField[i].getPosition().z)));
					}
				
					basic_shader1.SetModelviewMatrix(shader_modelview.top());
					basic_shader1.SetProjectionMatrix(shader_projection.top());
					basic_shader1.SetOffset((glutGet(GLUT_ELAPSED_TIME) - paused_time) / 1000.0f);
				}

				// Set up EarthMap shader
				glPushMatrix();
					glTranslateVector(ballsOnField[i].getPosition());
					glRotated(earthsRotation, 0, 1, 0);
					glRotated(-90+23, 1, 0, 0);
					
					glCallList(DisplayListIndex + DRAW_GLU_BALL);

				glPopMatrix();

				glBindTexture(GL_TEXTURE_2D, 0);
				glDisable(GL_TEXTURE_2D);
				glUseProgram(0);

				if (use_shader)
				{
					shader_projection.pop();
					shader_modelview.pop();
				}
			}
			else
			{
				// For GodView, only display targets on your level if hole is active
				if (GodView)
				{
					if((yourBall.aboveFloor != ballsOnField[i].aboveFloor) && Hole_Is_Active) continue;

					ballsOnField[i].lightBall(ballColor);
					ballsOnField[i].draw();
				}
				else
				{
					ballsOnField[i].lightBall(ballColor);
					ballsOnField[i].draw();
				}
				float * lightdir = new float[3];

				// Add Phong Shading to the targets
				if (PhongShading)
				{
					basic_shader4.UseProgram();
					ballsOnField[i].lightBall(ballColor);
					ballsOnField[i].draw();
				}
				else if (ToonBalls)
				{
					basic_shader5.UseProgram();

					Vector3 lightVec = Vector3(global_position[0] - ballsOnField[i].getPosition().x,
											   global_position[1] - ballsOnField[i].getPosition().y,
											   global_position[2] - ballsOnField[i].getPosition().z);

					lightdir[0] = lightVec.x;
					lightdir[1] = lightVec.y;
					lightdir[2] = lightVec.z;

					glUniform1fv(basic_shader5.lightDir, 1, lightdir);

					ballsOnField[i].lightBall(ballColor);

					glPushMatrix();
						glTranslateVector(ballsOnField[i].getPosition());
						glutSolidTeapot(50);
					glPopMatrix();
				}
				else
				{
					ballsOnField[i].lightBall(ballColor);
					ballsOnField[i].draw();
				}

				delete [] lightdir;
				glUseProgram(0);
			}
		}

		// Draw Warp Zone
		if (warp_zone_active)
		{
			warpZone.lightBall(warpZoneColor);
			warpZone.DrawWarpZone();
		}

		// Draw Player in the GodView
		if (GodView)
		{
			yourBall.lightBall(yourBallColor);
			glDisable(GL_LIGHTING);
			glPushMatrix();
				glTranslateVector(yourBall.getPosition());
				glRotated(-degreesLookingAt, 0, 1, 0);
				glCallList(DisplayListIndex + DRAW_PLAYER);
			glPopMatrix();
			glEnable(GL_LIGHTING);
		}
	glPopMatrix();
}

void DrawPlayer()
{
	Vector3 point1 = Vector3(0, 0, 2*BALL_RADIUS);
	Vector3 point2 = Vector3(-2*BALL_RADIUS, 0, 2*BALL_RADIUS).normalize() * 2 * BALL_RADIUS;
	Vector3 point3 = Vector3( 2*BALL_RADIUS, 0, 2*BALL_RADIUS).normalize() * 2 * BALL_RADIUS;
	Vector3 center = Vector3(0, 0, 0);

	glNewList(DisplayListIndex + DRAW_PLAYER, GL_COMPILE);
		glPushMatrix();
			glBegin(GL_TRIANGLES);
				glVertexVector(center);
				glVertexVector(point1);
				glVertexVector(point2);

				glVertexVector(center);
				glVertexVector(point1);
				glVertexVector(point3);
			glEnd();
		glPopMatrix();
	glEndList();
}

void updateBall(int value)
{
	if(paused)
	{
		glutTimerFunc(25, updateBall, value);
		return;
	}

	// Spin the warp zone
	if (!paused && warp_zone_active) Ball::angle += 1;
	if (Ball::angle > 360)           Ball::angle -= 360;

	// Rotate the Skybox
	if (!paused && rotateSkybox)   skyboxRotationAngle += 0.05;
	if (skyboxRotationAngle > 360) skyboxRotationAngle -= 360;

	// Rotate Earth
	if (!paused && EarthBall) earthsRotation += 0.5;
	if (earthsRotation > 360) earthsRotation -= 360;

	POINT mouse;
	GetCursorPos(&mouse);

	// Update global viewing angle
	degreesLookingAt += (((float)mouse.x - window_width/2)/window_width/2) * MOUSE_CONSTANT;

	while(degreesLookingAt > 360)
	{
		degreesLookingAt -= 360;
	}
	while(degreesLookingAt < 0)
	{ 
		degreesLookingAt += 360;
	}
	
	// Get the total time played
	double time = timer.TimeSinceLastCall();
	double totTime, totTime2 = time;

	// Update fireworks and stands
	for(int i = 0; i < activeFireworks.size(); i++)
	{
		activeFireworks[i].updateFirework(time);
	}
	for(int i = 0; i < activeStands.size(); i++)
	{
		activeStands[i].updatePeople(time);
	}

	// Handle billiards mode
	if(billardsOn)
	{
		// Create a temporary target for the player
		Ball tmp = Ball(yourBall.getPosition());
		tmp.setVelocity(yourBall.getVelocity());
		ballsOnField.push_back(tmp);

		// Handle player collisions 
		if(handleCollisions(tmp, yourBall.getNextPosition(time), degreesLookingAt, time))
		{
			if(mouse.y > window_height/2)
			{
				degreesLookingAt += 180;
				if(degreesLookingAt > 360)
					degreesLookingAt -= 360;
			}
		}

		// Handle non-player collisions
		for(int i = 0; i < ballsOnField.size() - 1; i++)
		{
			tmp = Ball(ballsOnField[i].getPosition());
			tmp.setVelocity(ballsOnField[i].getVelocity());
			float blankAngle = 0;
			double totTime = totTime2;
			handleCollisions(tmp, ballsOnField[i].getNextPosition(totTime), blankAngle, totTime);

		}

		ballsOnField.pop_back();

		yourBall.setPosition(tmpPosStorage[0]);
		yourBall.setVelocity(tmpVelStorage[0]);

		for(int i = 1; i < tmpPosStorage.size(); i++)
		{
			ballsOnField[i - 1].setPosition(tmpPosStorage[i]);
			ballsOnField[i - 1].setVelocity(tmpVelStorage[i]);
		}

		tmpPosStorage.clear();
		tmpVelStorage.clear();
	}
	else
	{
		// Handle player collisions 
		if(handleCollisions(yourBall, yourBall.getNextPosition(time), degreesLookingAt, time))
		{
			if(mouse.y > window_height/2)
			{
				degreesLookingAt += 180;
				if(degreesLookingAt > 360)
					degreesLookingAt -= 360;
			}
		}
	}

	// Get distance to warp zone
	Vector3 distToWarpZone = Vector3(yourBall.getPosition().x - warpZone.getPosition().x,
		                             yourBall.getPosition().y - warpZone.getPosition().y,
									 yourBall.getPosition().z - warpZone.getPosition().z);

	// If ball is in active Warp Zone, warp it
	if (warp_zone_active && distToWarpZone.magnitude() <= BALL_RADIUS*2) WarpBall();

	// Get distance to hole
	Vector3 distToHole = Vector3(yourBall.getPosition().x - hole.x,
		                         yourBall.getPosition().y - hole.y,
							     yourBall.getPosition().z - hole.z);
	
	// If ball is in active hole, warp it
	if (Hole_Is_Active && player_in_hole && distToHole.magnitude() <= BALL_RADIUS*2) 
	{
		player_in_hole = false;
		yourBall.aboveFloor = !yourBall.aboveFloor;
	}

	// If hole gets out of hole, reactive it
	if (Hole_Is_Active && !player_in_hole && distToHole.magnitude() > BALL_RADIUS*2) 
	{
		player_in_hole = true;
	}

	// Update global viewing angle
	getXandZonCircleWithRandTheta(degreesLookingAt, 1.0f, lookingAt[0], lookingAt[1]);
	
	// Update player velocity
	yourBall.setVelocity(Vector3(lookingAt[0], 0, lookingAt[1]).normalize()*((float)window_height/2 - mouse.y) );

	glutPostRedisplay();
	glutTimerFunc(25, updateBall, value);
}

void updateTimer(int value)
{
	if(paused)
	{
		glutTimerFunc(1000, updateTimer, value);
		return;
	}

	if(!ballsOnField[value].decTimer())
	{
		ballsOnField[value].ballHit = false;
	}
	else
	{
		glutTimerFunc(1000, updateTimer, value);
	}
}



void launchFireworks(int value)
{
	firework tmp;

	if(paused)
	{
		glutTimerFunc(500, launchFireworks, value);
		return;
	}

	// Determine which wall to have firework behind
	float ranNum = RangeRand(0,4);

	if(ranNum < 1)
	{
		tmp = firework(RangeRand(300,1500), Vector3(RangeRand(-3000, 3000), 0, -3000.0f), Vector3(0, 1300.0f, 0), RangeRand(800,1200));
	}
	else if(ranNum < 2)
	{
		tmp = firework(RangeRand(300,1500), Vector3(RangeRand(-3000, 3000), 0, 3000.0f), Vector3(0, 1300.0f, 0), RangeRand(800,1200));
	}
	else if(ranNum < 3)
	{
		tmp = firework(RangeRand(300,1500), Vector3(-3000.0f, 0, RangeRand(-3000, 3000)), Vector3(0, 1300.0f, 0), RangeRand(800,1200));
	}
	else
	{
		tmp = firework(RangeRand(300,1500), Vector3(3000.0f, 0, RangeRand(-3000, 3000)), Vector3(0, 1300.0f, 0), RangeRand(800,1200));
	}
	activeFireworks.push_back(tmp);

	if(fireworksOn)
	{
		glutTimerFunc(500, launchFireworks, value);
	}
}

// Handle all target collisions
bool handleCollisions(Ball &curBall, Vector3 nextPos, float &angle, double &time)
{
	bool collided = false;
	bool ballCollision = false;
	Vector3 nextVelocity, newNextPosition, newCurPosition;
	float tmpCollision, newAngle, timeTillHitWall;
	float closestCollision = -1;
	double timeFromStart = time;
	int numOfBallHit = -1;

	nextVelocity = curBall.getVelocity();

	// Back wall
	if(nextPos.z + BALL_RADIUS > HALF_MILE)
	{
		nextVelocity = curBall.getVelocity() - (Vector3(0, 0, curBall.getVelocity().z) * 2);
		closestCollision = abs((HALF_MILE - curBall.getPosition().z)/cos((Vector3(0,0,1).getAngle(yourBall.getVelocity()))));
		newCurPosition = curBall.getVelocity().normalize() * (closestCollision - 50);
		timeTillHitWall = (newCurPosition - curBall.getPosition()).magnitude() / curBall.getVelocity().magnitude();
		time -= timeTillHitWall;
		closestCollision = timeTillHitWall;
		newNextPosition = Vector3(nextPos.x, 0, HALF_MILE - (nextPos.z - HALF_MILE + 2*BALL_RADIUS));

		if(degreesLookingAt >= 180)
		{
			newAngle = 2*270 - angle;
		}
		else
		{
			newAngle = 180 - angle;
		}

		collided = true;
	}

	// Top wall
	if(nextPos.z - BALL_RADIUS < -HALF_MILE)
	{
		tmpCollision = abs((-HALF_MILE - curBall.getPosition().z)/cos((Vector3(0,0,-1).getAngle(curBall.getVelocity()))));
		if((tmpCollision < closestCollision) || closestCollision == -1)
		{
			closestCollision = tmpCollision;
			newCurPosition = curBall.getVelocity().normalize() * (closestCollision - 50);
			timeTillHitWall = (newCurPosition - curBall.getPosition()).magnitude() / curBall.getVelocity().magnitude();
			time -= timeTillHitWall;
			closestCollision = timeTillHitWall;
			nextVelocity = curBall.getVelocity() - (Vector3(0, 0, curBall.getVelocity().z) * 2);
			newNextPosition = Vector3(nextPos.x, 0, -HALF_MILE - (nextPos.z + HALF_MILE - 2*BALL_RADIUS));

			if(degreesLookingAt <= 180)
			{
				newAngle = 2*270 - angle;
			}
			else
			{
				newAngle = 180 - angle;
			}

			collided = true;
		}
	}

	// Left wall
	if(nextPos.x - BALL_RADIUS < -HALF_MILE)
	{
		tmpCollision = abs((-HALF_MILE - curBall.getPosition().x)/cos((Vector3(-1,0,0).getAngle(curBall.getVelocity()))));
		if((tmpCollision < closestCollision) || closestCollision == -1)
		{
			closestCollision = tmpCollision;
			newCurPosition = curBall.getVelocity().normalize() * (closestCollision - 50);
			timeTillHitWall = (newCurPosition - curBall.getPosition()).magnitude() / curBall.getVelocity().magnitude();
			time -= timeTillHitWall;
			closestCollision = timeTillHitWall;
			nextVelocity = curBall.getVelocity() - (Vector3(curBall.getVelocity().x, 0, 0) * 2);
			newNextPosition = Vector3(-HALF_MILE - (nextPos.x + HALF_MILE - 2*BALL_RADIUS), 0, nextPos.z);

			if(degreesLookingAt <= 270)
			{
				newAngle = 2*180 - angle;
			}
			else
			{
				newAngle = 360 - angle;
			}

			collided = true;
		}
	}

	// Right wall
	if(nextPos.x + BALL_RADIUS > HALF_MILE)
	{
		tmpCollision = abs((HALF_MILE - curBall.getPosition().x)/cos((Vector3(1,0,0).getAngle(curBall.getVelocity()))));
		if((tmpCollision < closestCollision) || closestCollision == -1)
		{
			closestCollision = tmpCollision;
			newCurPosition = curBall.getVelocity().normalize() * (closestCollision - 50);
			timeTillHitWall = (newCurPosition - curBall.getPosition()).magnitude() / curBall.getVelocity().magnitude();
			time -= timeTillHitWall;
			closestCollision = timeTillHitWall;
			nextVelocity = curBall.getVelocity() - (Vector3(curBall.getVelocity().x, 0, 0) * 2);
			newNextPosition = Vector3(HALF_MILE - (nextPos.x - HALF_MILE + 2*BALL_RADIUS), 0, nextPos.z);

			if(degreesLookingAt <= 270)
			{
				newAngle = 2*180 - angle;
			}
			else
			{
				newAngle = 360 - angle;
			}

			collided = true;
		}
	}

	// Ball detection
	for(int i = 0; i < (int)ballsOnField.size(); i++)
	{
		if(&curBall == &ballsOnField[i]) continue;

		if(curBall.resolveCollision(ballsOnField[i], closestCollision, nextVelocity, newNextPosition, newCurPosition, nextPos, time, timeFromStart))
		{
			collided = true;
			ballCollision = true;
			numOfBallHit = i;

			if(nextVelocity.x <= 0 && nextVelocity.z <= 0)
			{
				newAngle = 360 - ((nextVelocity.getAngle(Vector3(0, 0, -1))) * 180/ PI);
			}
			else if(nextVelocity.x <= 0 && nextVelocity.z >= 0) 
			{
				newAngle = 360 - ((nextVelocity.getAngle(Vector3(0, 0, -1))) * 180/ PI);
			}
			else if(nextVelocity.x >= 0 && nextVelocity.z <= 0) 
			{
				newAngle = (nextVelocity.getAngle(Vector3(0, 0, -1))) * 180 / PI;
			}
			else
			{
				newAngle = (nextVelocity.getAngle(Vector3(0, 0, -1))) * 180 / PI;
			}
		}
	}
	

	
	if(collided)
	{
		curBall.setVelocity(nextVelocity);
		curBall.setPosition(newCurPosition);
		angle = newAngle;	

		if(numOfBallHit != -1)
		{
			if(!ballsOnField[numOfBallHit].ballHit)
			{
				if(numOfBallHit != totalBallsOnField)
				{
					glutTimerFunc(1000, updateTimer, numOfBallHit);
				}
				
			}
			if(numOfBallHit != totalBallsOnField)
			{
				ballsOnField[numOfBallHit].startTimer();
			}
			
		}

		handleCollisions(curBall, newNextPosition, newAngle, time);
	}
	else
	{
		if(billardsOn)
		{
			tmpPosStorage.push_back(nextPos);
			tmpVelStorage.push_back(nextVelocity);
		}
		else
		{
			curBall.setPosition(nextPos);
			curBall.setVelocity(nextVelocity);
		}
	}

	return ballCollision;
}


void createCrowd(int value)
{
	if(activeStands[value].createPerson())
		glutTimerFunc(400, createCrowd, value);
}

void drawPlus()
{
	glDisable(GL_LIGHTING);

	glPushMatrix();
		glLoadIdentity();

		glColor3f(1, 1, 1);
		glLineWidth(2.0f);

		glTranslated(0, 0, -1);

		glBegin( GL_LINES );
			glVertex3d(-PLUS_SIZE, 0, 0);
			glVertex3d( PLUS_SIZE, 0, 0);

			glVertex3d(0, -PLUS_SIZE, 0);
			glVertex3d(0,  PLUS_SIZE, 0);
		glEnd();
	glPopMatrix();

	glEnable(GL_LIGHTING);
}